Skip to content

Fixes #3023 - BREAKING CHANGE - Configurable key bindings via ConfigurationManager#4824

Merged
tig merged 82 commits into
v2_developfrom
feature/cm-keybindings
Mar 13, 2026
Merged

Fixes #3023 - BREAKING CHANGE - Configurable key bindings via ConfigurationManager#4824
tig merged 82 commits into
v2_developfrom
feature/cm-keybindings

Conversation

@tig

@tig tig commented Mar 10, 2026

Copy link
Copy Markdown
Member

Fixes

Summary

Makes all built-in key bindings configurable via ConfigurationManager with platform-specific support (Windows / Linux / macOS). The design is MEC-ready (Microsoft.Extensions.Configuration) to minimize future migration cost.

What Changed (140 files, +4065/-1368)

New infrastructure:

  • PlatformKeyBinding POCO record with All/Windows/Linux/Macos key slots
  • Bind factory class with ergonomic helpers: Bind.All(), Bind.AllPlus(), Bind.NonWindows(), Bind.Platform()
  • TuiPlatform enum replacing string-based platform identification
  • KeyBindingSchemaConverter for JSON serialization of Dictionary<Command, PlatformKeyBinding>
  • View.ApplyKeyBindings() — layered binding resolution using GetSupportedCommands() filter

Three [ConfigurationProperty] properties (the entire CM surface):

  1. Application.DefaultKeyBindings — app-level commands (Quit, Arrange, Tab navigation, Suspend, Refresh)
  2. View.DefaultKeyBindings — shared base layer (cursor nav, clipboard, selection, editing)
  3. View.ViewKeyBindings — per-view-type overrides merged dictionary

13 views migrated with view-specific DefaultKeyBindings:
TextField, TextView, ListView, TreeView, HexView, DropDownList, LinearRange, DateEditor, TimeEditor, TabView, TableView, CharMap, ColorPicker

Breaking changes (Phase 12):

  • Removed single-key properties from ApplicationKeyboard (QuitKey, ArrangeKey, NextTabStopKey, etc.) — replaced by Application.DefaultKeyBindings[Command.Quit] dictionary pattern
  • PlatformKeyBinding stores Key[] instead of string[] (type-safe)
  • Moved Bind and PlatformKeyBinding from Configuration/ to Input/Keyboard/
  • Application.GetDefaultKey(Command) and Application.GetDefaultKeys(Command) helper methods added

Other improvements:

  • Standardized MenuBar activation from F9→F10 (cross-platform consistency)
  • Fixed TextField Emacs keybindings (Ctrl+B/F) to bind on all platforms
  • Fixed Undo/Redo bindings to include Ctrl+Z/Y on all platforms
  • Added Configuration and Draw trace categories
  • Rewrote KeyBindings UICatalog scenario with 3-column layout

Architecture

┌──────────────────────────────────────────────────────────────┐
│  User config.json (overrides)                                │
│  "View.ViewKeyBindings": {                                   │
│    "TextField": { "Undo": { "All": ["Ctrl+Z"] } }           │
│  }                                                           │
└──────────────┬───────────────────────────────────────────────┘
               │ CM loads & replaces static property
               ▼
┌──────────────────────────────────────────────────────────────┐
│  C# Static Properties (source of truth)                      │
│  [ConfigurationProperty] — only 3 properties:                │
│                                                              │
│  Application.DefaultKeyBindings  ← Layer 1 (app-level)      │
│  View.DefaultKeyBindings         ← Layer 2 (shared base)    │
│  View.ViewKeyBindings            ← User overrides (merged)  │
│                                                              │
│  Plain statics (no [ConfigurationProperty]):                 │
│  TextField.DefaultKeyBindings    ← Layer 3 (view-specific)  │
│  ListView.DefaultKeyBindings     ← Layer 3 (view-specific)  │
│  ...                                                         │
└──────────────┬───────────────────────────────────────────────┘
               │ view.ApplyKeyBindings(layers...)
               ▼
┌──────────────────────────────────────────────────────────────┐
│  Platform Resolution (PlatformKeyBinding POCO)               │
│  Windows: All + Windows                                      │
│  Linux:   All + Linux                                        │
│  macOS:   All + Macos                                        │
└──────────────┬───────────────────────────────────────────────┘
               │ GetSupportedCommands() filter
               ▼
┌──────────────────────────────────────────────────────────────┐
│  view.KeyBindings.Add (key, command)                         │
│  Only for commands the view has registered handlers for      │
└──────────────────────────────────────────────────────────────┘

Design Principles

  • C# code is source of truth — defaults in static initializers, works without CM
  • config.json is for user overrides only — built-in config has zero key binding entries
  • Per-command platform attribution — each command specifies platform keys via PlatformKeyBinding
  • Skip unhandled commandsApplyKeyBindings() checks GetSupportedCommands() filter
  • MEC-ready POCOsPlatformKeyBinding maps cleanly to IOptions<T> binding

Example user config.json

{
  "Application.DefaultKeyBindings": {
    "Quit": { "All": ["Ctrl+Q"] },
    "Suspend": { "Linux": ["Ctrl+Z"], "Macos": ["Ctrl+Z"] }
  },
  "View.ViewKeyBindings": {
    "TextField": {
      "Undo": { "All": ["Ctrl+Z"] },
      "DeleteCharRight": { "All": ["Delete"], "Linux": ["Ctrl+D"], "Macos": ["Ctrl+D"] }
    }
  }
}

Documentation Updated

  • keyboard.md — rewrote for DefaultKeyBindings dictionary API
  • config.md — updated config examples and property references
  • navigation.md — replaced removed IKeyboard.*Key property refs
  • newinv2.md — updated v2 migration notes
  • arrangement.md, menus.md, migratingfromv1.md — fixed stale refs
  • example_config.json — comprehensive refresh with current properties

Pull Request checklist:

  • I've named my PR in the form of "Fixes #issue. Terse description."
  • My code follows the style guidelines of Terminal.Gui
  • My code follows the Terminal.Gui library design guidelines
  • I ran dotnet test before commit
  • I have made corresponding changes to the API documentation (using /// style comments)
  • My changes generate no new warnings
  • I have checked my code and corrected any poor grammar or misspellings
  • I conducted basic QA to assure all features are working

Copilot AI and others added 30 commits October 3, 2025 19:04
tig and others added 4 commits March 12, 2026 18:26
Introduce ApplicationKeyboardThreadSafetyTests to verify thread-safety under various concurrent scenarios, including concurrent addition, invocation, event subscription, property mutation, and disposal. Tests use multiple threads to stress the API and assert no unexpected exceptions occur. Shared/static state is restored after tests to prevent side effects. This ensures ApplicationKeyboard is robust against race conditions in multi-threaded environments.
- Move all tests that mutate static state (e.g., Application.DefaultKeyBindings, Trace) into dedicated, non-parallelizable files/namespaces to prevent cross-test interference.
- Move and update TestLogging helper to UnitTests.Parallelizable for consistent logging in tests.
- Clean up KeyboardTests.cs by relocating setter tests to KeyboardSetterTests.cs.
- Add try/finally blocks to restore static state in tests that mutate it.
- Update usings and namespaces for clarity and parallelization.
- Add comments and safeguards to prevent accidental static state pollution.
- Enable safe parallel execution for the majority of the test suite, improving reliability and performance.
The Keyboard directory exclusion was preventing moved test files from being
discovered by the test runner.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… generic type names, fix ListView layer order, apply DateEditor/TimeEditor bindings, update docs

- Make Bind class public so consumers can configure DefaultKeyBindings
- Fix Trace.Keyboard to use string.Join instead of .ToString() on IEnumerable
- Strip generic arity suffix in ApplyKeyBindings (TreeView\1 -> TreeView)
- Fix ListView to apply its layer before base layer (Home/End override)
- Add ApplyKeyBindings call in DateEditor/TimeEditor constructors
- Update DefaultKeyBindingsChanged doc noting mutation limitation
- Fix config.md JSON examples to use proper nested structure
- Update TreeView/ListView comments for clarity

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tig tig requested review from BDisp and tznind March 13, 2026 12:49
@tig

tig commented Mar 13, 2026

Copy link
Copy Markdown
Member Author

@tznind what are your thoughts on this new design?

Comment thread Examples/UICatalog/Scenarios/KeyBindings.cs

@BDisp BDisp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This very arduous work seems perfect to me. I only asked a question.

Comment thread Terminal.Gui/Drivers/PlatformDetection.cs Outdated
tig added 3 commits March 13, 2026 13:57
- Use explicit EventArgs<T> and ValueChangedEventArgs<T> in event invocations for clarity and type safety.
- Replace Tracing.Trace with Trace for tracing calls; add global using for Terminal.Gui.Tracing.
- Remove obsolete PlatformDetection.GetCurrentPlatformName and its test.
- Clean up unused usings and improve code consistency.
Refactored DriverRegistry for conciseness by using expression-bodied members and single-line record declarations. Updated built-in driver registration to a more compact style. Changed GetDefaultDriver to return the "ANSI" driver by default on Windows platforms instead of the "WINDOWS" driver.
@tig

tig commented Mar 13, 2026

Copy link
Copy Markdown
Member Author

This very arduous work seems perfect to me. I only asked a question.

Since I forgto before, I also made ansi the default driver on all patforms here.,

@tig tig merged commit 15aec41 into v2_develop Mar 13, 2026
11 checks passed
@tig tig deleted the feature/cm-keybindings branch March 13, 2026 23:33
tig added a commit to tig/Terminal.Gui that referenced this pull request Mar 24, 2026
Resolves merge conflicts from v2_develop changes:
- tui-cs#4836: Lazy Adornment Views (IAdornment + IAdornmentView split)
- tui-cs#4835: Adornment transparency
- tui-cs#4824: Configurable key bindings
- tui-cs#4845: Test project restructuring

Adapted TabView/TabRow to new AdornmentImpl architecture:
- Padding.Add -> Padding.GetOrCreateView().Add
- Border.Frame -> Border.GetFrame()
- Border.GetBorderRectangle -> BorderView.GetBorderBounds (made internal)
- Adornment type refs -> AdornmentView/AdornmentImpl
- Added LineCanvas merge from Border/Padding SubViews into parent View
- Fixed nullable LineStyle conversion in TabRow

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable overriding default Key Bindings with ConfigurationManager

5 participants